package sim;
import java.io.File;
import java.io.FileNotFoundException;


/**
 * IDStage: The instruction decoder pipe stage. Contains the register file and instruction decoder.
 * On a cycle(), gets instruction from IFStage and decodes it. Checks for hazards by asking the 
 * HazardController about each operand. Forwards data if available. Stalls if necessary. Gets
 * operands from register file if no hazards. On a branch, if taken, updates PC of IF stage and
 * causes a bubble to be inserted.
 * @author Piotr
 *
 */
public class IDStage extends PipeStage {
	
	private RegisterFile regFile;
	private InstructionDecoder instrDec;
	
	IDStage() {
		super();
		regFile = new RegisterFile();
		instrDec = new InstructionDecoder();
	}
	
	IDStage(File dataFile) {
		super();
		try {
			regFile = new RegisterFile(dataFile);
		} catch (FileNotFoundException e) {
			System.out.println("Error reading " + dataFile 
					+ ". Did not initialize register file.");
			regFile = new RegisterFile();
		}
		instrDec = new InstructionDecoder();
		return;
	}
	
	
	
	/**
	 * cycle method. Gets instruction from IFStage. 
	 */
	int cycle() throws InvalidInstructionException {
		if (!stalled) {
			instr = Simulator.ifs.passInstruction();
			if (instr != null && !instr.isNop()) {
				doCycle();
				return 1;
			}
			return 0;
		} else if (causedStall) {
			causedStall = false;
			stalled = false;
			if (instr != null && !instr.isNop()) {
				doCycle();
				return 1;
			}
			return 0;
		} else {
			stalled = false;
			if (instr != null && !instr.isNop()) {
				return 1;
			} else {
				return 0;
			}
		}
	}
	
	private void doCycle() throws InvalidInstructionException {
		if (instrDec.decodeInstruction(instr) == 1) {
			throw new InvalidInstructionException(instr.address);
		}
		readOperands(instr);
		if (instr.iClass.equals(InstructionClass.BRANCH)) {
			doBranch(instr);
		}
	}

	/**
	 * Function to read operands. Checks for hazards by asking the hazard controller.
	 * If a hazard exists, checks if a value can be forwarded immediately.
	 * @param instr
	 * @throws InvalidInstructionException
	 */
	private void readOperands(Instruction instr) throws InvalidInstructionException {
		try {
			if (instr.arg1Reg != -1) {
				if (Simulator.hzc.checkForHazard(instr.arg1Reg)) {
					Integer i = Simulator.hzc.tryForwarding(instr.arg1Reg, this.getClass());
					if (i != null) {
						instr.arg1Val = i;
						instr.arg1Ready = true;
					} else {
						instr.arg1Ready = false;
					}
				} else {
					instr.arg1Val = regFile.readReg(instr.arg1Reg);
					instr.arg1Ready = true;
				}
			}
			if (instr.arg2Reg != -1) {
				if (Simulator.hzc.checkForHazard(instr.arg2Reg)) {
					Integer i = Simulator.hzc.tryForwarding(instr.arg2Reg, this.getClass());
					if (i != null) {
						instr.arg2Val = i;
						instr.arg2Ready = true;
					} else {
						instr.arg2Ready = false;
					}
				} else {
					instr.arg2Val = regFile.readReg(instr.arg2Reg);
					instr.arg2Ready = true;
				}
			}
		} catch (RegisterIndexOutOfBoundsException e) {
			throw new InvalidInstructionException(instr.address);
		}
	}
	/**
	 * Determines whether the branch is taken and performs it if it is. Causes a stall
	 * if a value is not available.
	 * @param instr
	 */
	private void doBranch(Instruction instr) {
		if (!instr.arg1Ready) {
			Simulator.hzc.stallPipe(this.getClass());
		} else {
			switch (instr.iType) {
			case BEZ:
				if (instr.arg1Val == 0) {
					instr.branchTaken = true;
					instr.memAddr = instr.address + instr.arg2Val + 1;
					Simulator.ifs.branch(instr.memAddr);
				}
				break;
			case BLZ:
				if (instr.arg1Val < 0) {
					instr.branchTaken = true;
					instr.memAddr = instr.address + instr.arg2Val + 1;
					Simulator.ifs.branch(instr.memAddr);
				}
				break;
			case JALR:
				instr.branchTaken = true;
				instr.memAddr = instr.arg1Val;
				Simulator.ifs.branch(instr.memAddr);
				break;
			default:
				instr.branchTaken = false;
			}
		}
	}
	
	/**
	 * Writes to the register file
	 * @param regAddr 
	 * @param value
	 */
	void writeBack(int index, int value) throws RegisterIndexOutOfBoundsException {
		regFile.writeReg(index, value);
	}
	
	/**
	 * Returns a dump of the register file.
	 * @return
	 */
	String dumpRegisters() {
		return regFile.toString();
	}
	
}
